function mxObjectCodec(template, exclude, idrefs, mapping)
{
	this.template = template;
	this.exclude = exclude || [];
	this.idrefs = idrefs || [];
	this.mapping = mapping || {};
	this.reverse = {};

	for (var i in this.mapping)
	{
		this.reverse[this.mapping[i]] = i;
	}
};
mxObjectCodec.prototype.template = null;
mxObjectCodec.prototype.exclude = null;
mxObjectCodec.prototype.idrefs = null;
mxObjectCodec.prototype.mapping = null;
mxObjectCodec.prototype.reverse = null;
mxObjectCodec.prototype.cloneTemplate = function()
{
	return new this.template.constructor();
};
mxObjectCodec.prototype.getFieldName = function(attributename)
{
	if (attributename != null)
	{
		var mapped = this.reverse[attributename];

		if (mapped != null)
		{
			attributename = mapped;
		}
	}
	return attributename;
};
mxObjectCodec.prototype.getAttributeName = function(fieldname)
{
	if (fieldname != null)
	{
		var mapped = this.mapping[fieldname];

		if (mapped != null)
		{
			fieldname = mapped;
		}
	}
	return fieldname;
};
mxObjectCodec.prototype.isExcluded = function(obj, attr, value, isWrite)
{
	return attr == mxObjectIdentity.FIELD_NAME || mxUtils.indexOf(this.exclude, attr) >= 0;
};
mxObjectCodec.prototype.isReference = function(obj, attr, value, isWrite)
{
	return mxUtils.indexOf(this.idrefs, attr) >= 0;
};
mxObjectCodec.prototype.encode = function(enc, obj)
{
	var name = mxUtils.getFunctionName(obj.constructor);
	var node = enc.document.createElement(name);
	obj = this.beforeEncode(enc, obj, node);
	this.encodeObject(enc, obj, node);
	return this.afterEncode(enc, obj, node);
};
mxObjectCodec.prototype.encodeObject = function(enc, obj, node)
{
	enc.setAttribute(node, 'id', enc.getId(obj));

	for (var i in obj)
	{
		var name = i;
		var value = obj[name];

		if (value != null && !this.isExcluded(obj, name, value, true))
		{
			if (mxUtils.isNumeric(name))
			{
				name = null;
			}
			this.encodeValue(enc, obj, name, value, node);
		}
	}
};
mxObjectCodec.prototype.encodeValue = function(enc, obj, name, value, node)
{
	if (value != null)
	{
		if (this.isReference(obj, name, value, true))
		{
			var tmp = enc.getId(value);

			if (tmp == null)
			{
				mxLog.warn('mxObjectCodec.encode: No ID for ' + mxUtils.getFunctionName(obj.constructor) + '.' + name
					+ '=' + value);
				return;
			}
			value = tmp;
		}
		var defaultValue = this.template[name];

		if (name == null || enc.encodeDefaults || defaultValue != value)
		{
			name = this.getAttributeName(name);
			this.writeAttribute(enc, obj, name, value, node);
		}
	}
};
mxObjectCodec.prototype.writeAttribute = function(enc, obj, attr, value, node)
{
	if (typeof (value) != 'object')
	{
		this.writePrimitiveAttribute(enc, obj, attr, value, node);
	}
	else
	{
		this.writeComplexAttribute(enc, obj, attr, value, node);
	}
};
mxObjectCodec.prototype.writePrimitiveAttribute = function(enc, obj, attr, value, node)
{
	value = this.convertValueToXml(value);

	if (attr == null)
	{
		var child = enc.document.createElement('add');

		if (typeof (value) == 'function')
		{
			child.appendChild(enc.document.createTextNode(value));
		}
		else
		{
			enc.setAttribute(child, 'value', value);
		}
		node.appendChild(child);
	}
	else if (typeof (value) != 'function')
	{
		enc.setAttribute(node, attr, value);
	}
};
mxObjectCodec.prototype.writeComplexAttribute = function(enc, obj, attr, value, node)
{
	var child = enc.encode(value);

	if (child != null)
	{
		if (attr != null)
		{
			child.setAttribute('as', attr);
		}
		node.appendChild(child);
	}
	else
	{
		mxLog.warn('mxObjectCodec.encode: No node for ' + mxUtils.getFunctionName(obj.constructor) + '.' + attr + ': '
			+ value);
	}
};
mxObjectCodec.prototype.convertValueToXml = function(value)
{
	if (typeof (value.length) == 'undefined' && (value == true || value == false))
	{
		value = (value == true) ? '1' : '0';
	}
	return value;
};
mxObjectCodec.prototype.convertValueFromXml = function(value)
{
	if (mxUtils.isNumeric(value))
	{
		value = parseFloat(value);
	}
	return value;
}
mxObjectCodec.prototype.beforeEncode = function(enc, obj, node)
{
	return obj;
};
mxObjectCodec.prototype.afterEncode = function(enc, obj, node)
{
	return node;
};
mxObjectCodec.prototype.decode = function(dec, node, into)
{
	var id = node.getAttribute('id');
	var obj = dec.objects[id];

	if (obj == null)
	{
		obj = into || this.cloneTemplate();

		if (id != null)
		{
			dec.putObject(id, obj);
		}
	}
	node = this.beforeDecode(dec, node, obj);
	this.decodeNode(dec, node, obj);
	return this.afterDecode(dec, node, obj);
};
mxObjectCodec.prototype.decodeNode = function(dec, node, obj)
{
	if (node != null)
	{
		this.decodeAttributes(dec, node, obj);
		this.decodeChildren(dec, node, obj);
	}
};
mxObjectCodec.prototype.decodeAttributes = function(dec, node, obj)
{
	var type = mxUtils.getFunctionName(obj.constructor);
	var attrs = node.attributes;

	if (attrs != null)
	{
		for (var i = 0; i < attrs.length; i++)
		{
			this.decodeAttribute(dec, attrs[i], obj);
		}
	}
};
mxObjectCodec.prototype.decodeAttribute = function(dec, attr, obj)
{
	var name = attr.nodeName;

	if (name != 'as' && name != 'id')
	{
		var value = this.convertValueFromXml(attr.nodeValue);
		var fieldname = this.getFieldName(name);

		if (this.isReference(obj, fieldname, value, false))
		{
			var tmp = dec.getObject(value);

			if (tmp == null)
			{
				mxLog.warn('mxObjectCodec.decode: No object for ' + mxUtils.getFunctionName(obj.constructor) + '.'
					+ name + '=' + value);
				return;
			}
			value = tmp;
		}

		if (!this.isExcluded(obj, name, value, false))
		{
			obj[name] = value;
		}
	}
};
mxObjectCodec.prototype.decodeChildren = function(dec, node, obj)
{
	var type = mxUtils.getFunctionName(obj.constructor);
	var child = node.firstChild;

	while (child != null)
	{
		var tmp = child.nextSibling;

		if (child.nodeType == mxConstants.NODETYPE_ELEMENT && !this.processInclude(dec, child, obj))
		{
			this.decodeChild(dec, child, obj);
		}
		child = tmp;
	}
};
mxObjectCodec.prototype.decodeChild = function(dec, child, obj)
{
	var fieldname = this.getFieldName(child.getAttribute('as'));

	if (fieldname == null || !this.isExcluded(obj, fieldname, child, false))
	{
		var value = null;
		var template = obj[fieldname];

		if (child.nodeName == 'add')
		{
			value = child.getAttribute('value');

			if (value == null)
			{
				value = mxUtils.eval(mxUtils.getTextContent(child));
			}
		}
		else
		{
			if (template instanceof Array && template.length > 0)
			{
				template = null;
			}
			value = dec.decode(child, template);
		}

		if (value != null && value != template)
		{
			if (fieldname != null && fieldname.length > 0)
			{
				obj[fieldname] = value;
			}
			else
			{
				obj.push(value);
			}
		}
	}
};
mxObjectCodec.prototype.processInclude = function(dec, node, into)
{
	if (node.nodeName == 'include')
	{
		var name = node.getAttribute('name');

		if (name != null)
		{
			try
			{
				var xml = mxUtils.load(name).getDocumentElement();

				if (xml != null)
				{
					dec.decode(xml, into);
				}
			}
			catch (e)
			{
			}
		}
		return true;
	}
	return false;
};
mxObjectCodec.prototype.beforeDecode = function(dec, node, obj)
{
	return node;
};
mxObjectCodec.prototype.afterDecode = function(dec, node, obj)
{
	return obj;
};

mxCodecRegistry.register(function()
{
	var codec = new mxObjectCodec(new mxCell(), ['children', 'edges', 'overlays', 'mxTransient'], ['parent', 'source',
		'target']);
	codec.isExcluded = function(obj, attr, value, isWrite)
	{
		return mxObjectCodec.prototype.isExcluded.apply(this, arguments)
			|| (isWrite && attr == 'value' && value.nodeType == mxConstants.NODETYPE_ELEMENT);
	};
	codec.afterEncode = function(enc, obj, node)
	{
		if (obj.value != null && obj.value.nodeType == mxConstants.NODETYPE_ELEMENT)
		{
			var tmp = node;
			node = (mxClient.IS_IE) ? obj.value.cloneNode(true) : enc.document.importNode(obj.value, true);
			node.appendChild(tmp);

			var id = tmp.getAttribute('id');
			node.setAttribute('id', id);
			tmp.removeAttribute('id');
		}
		return node;
	};
	codec.beforeDecode = function(dec, node, obj)
	{
		var inner = node;
		var className = mxUtils.getFunctionName(this.template.constructor);

		if (node.nodeName != className)
		{

			var tmp = node.getElementsByTagName(className)[0];

			if (tmp != null && tmp.parentNode == node)
			{
				mxUtils.removeWhitespace(tmp, true);
				mxUtils.removeWhitespace(tmp, false);
				tmp.parentNode.removeChild(tmp);
				inner = tmp;
			}
			else
			{
				inner = null;
			}
			obj.value = node.cloneNode(true);
			var id = obj.value.getAttribute('id');

			if (id != null)
			{
				obj.setId(id);
				obj.value.removeAttribute('id');
			}
		}
		else
		{
			obj.setId(node.getAttribute('id'));
		}

		if (inner != null)
		{
			for (var i = 0; i < this.idrefs.length; i++)
			{
				var attr = this.idrefs[i];
				var ref = inner.getAttribute(attr);

				if (ref != null)
				{
					inner.removeAttribute(attr);
					var object = dec.objects[ref] || dec.lookup(ref);

					if (object == null)
					{
						var element = dec.getElementById(ref);

						if (element != null)
						{
							var decoder = mxCodecRegistry.codecs[element.nodeName] || this;
							object = decoder.decode(dec, element);
						}
					}
					obj[attr] = object;
				}
			}
		}
		return inner;
	};
	return codec;
}());

mxCodecRegistry.register(function()
{
	var codec = new mxObjectCodec(new mxGraphModel());
	codec.encode = function(enc, obj)
	{
		var name = mxUtils.getFunctionName(obj.constructor);
		var node = enc.document.createElement(name);
		var rootNode = enc.document.createElement('root');
		enc.encodeCell(obj.getRoot(), rootNode);
		node.appendChild(rootNode);
		return node;
	};
	codec.decodeChild = function(dec, child, obj)
	{
		if (child.nodeName == 'root')
		{
			this.decodeRoot(dec, child, obj);
		}
		else
		{
			mxObjectCodec.prototype.decodeChild.apply(this, arguments);
		}
	};
	codec.decodeRoot = function(dec, root, model)
	{
		var rootCell = null;
		var tmp = root.firstChild;

		while (tmp != null)
		{
			var cell = dec.decodeCell(tmp);

			if (cell != null && cell.getParent() == null)
			{
				rootCell = cell;
			}
			tmp = tmp.nextSibling;
		}

		if (rootCell != null)
		{
			model.setRoot(rootCell);
		}
	};
	return codec;
}());

mxCodecRegistry.register(function()
{
	var codec = new mxObjectCodec(new mxRootChange(), ['model', 'previous', 'root']);
	codec.afterEncode = function(enc, obj, node)
	{
		enc.encodeCell(obj.root, node);
		return node;
	};
	codec.beforeDecode = function(dec, node, obj)
	{
		if (node.firstChild != null && node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
		{
			var tmp = node.firstChild;
			obj.root = dec.decodeCell(tmp, false);
			var tmp2 = tmp.nextSibling;
			tmp.parentNode.removeChild(tmp);
			tmp = tmp2;

			while (tmp != null)
			{
				var tmp2 = tmp.nextSibling;
				dec.decodeCell(tmp);
				tmp.parentNode.removeChild(tmp);
				tmp = tmp2;
			}
		}
		return node;
	};
	codec.afterDecode = function(dec, node, obj)
	{
		obj.previous = obj.root;
		return obj;
	};
	return codec;
}());

mxCodecRegistry.register(function()
{
	var codec = new mxObjectCodec(new mxChildChange(), ['model', 'previous', 'previousIndex', 'child'], ['parent']);
	codec.isReference = function(obj, attr, value, isWrite)
	{
		if (attr == 'child' && (obj.previous != null || !isWrite))
		{
			return true;
		}
		return mxUtils.indexOf(this.idrefs, attr) >= 0;
	};
	codec.afterEncode = function(enc, obj, node)
	{
		if (this.isReference(obj, 'child', obj.child, true))
		{
			node.setAttribute('child', enc.getId(obj.child));
		}
		else
		{

			enc.encodeCell(obj.child, node);
		}
		return node;
	};
	codec.beforeDecode = function(dec, node, obj)
	{
		if (node.firstChild != null && node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
		{
			var tmp = node.firstChild;
			obj.child = dec.decodeCell(tmp, false);

			obj.child.setParent(null);
			var tmp2 = tmp.nextSibling;
			tmp.parentNode.removeChild(tmp);
			tmp = tmp2;

			while (tmp != null)
			{
				var tmp2 = tmp.nextSibling;

				if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
				{
					var id = tmp.getAttribute('id');

					if (dec.lookup(id) == null)
					{
						dec.decodeCell(tmp);
					}
				}
				tmp.parentNode.removeChild(tmp);
				tmp = tmp2;
			}
		}
		else
		{
			var childRef = node.getAttribute('child');
			obj.child = dec.getObject(childRef);
		}
		return node;
	};
	codec.afterDecode = function(dec, node, obj)
	{
		obj.previous = obj.parent;
		obj.previousIndex = obj.index;
		return obj;
	};
	return codec;
}());

mxCodecRegistry.register(function()
{
	var codec = new mxObjectCodec(new mxTerminalChange(), ['model', 'previous'], ['cell', 'terminal']);
	codec.afterDecode = function(dec, node, obj)
	{
		obj.previous = obj.terminal;
		return obj;
	};
	return codec;
}());